package org.jsola.hr.service.impl;

import groovy.lang.Lazy;
import lombok.extern.slf4j.Slf4j;
import org.jsola.admin.constant.AsyncTaskTypeEnum;
import org.jsola.admin.constant.TaskStatusEnum;
import org.jsola.admin.constant.TaskSubjectResultEnum;
import org.jsola.admin.entity.AsyncTaskDO;
import org.jsola.admin.entity.AsyncTaskDetailDO;
import org.jsola.admin.service.IAsyncTaskDetailService;
import org.jsola.admin.service.IAsyncTaskService;
import org.jsola.approval.vo.ApproFlowListVO;
import org.jsola.common.DateKit8;
import org.jsola.contract.dto.MemberVariableDTO;
import org.jsola.contract.dto.PublishSignDTO;
import org.jsola.contract.dto.PublishSignSigngleDTO;
import org.jsola.contract.dto.SignWeiXinSendDTO;
import org.jsola.contract.entity.CategoryDO;
import org.jsola.contract.entity.SealDO;
import org.jsola.contract.service.ISignService;
import org.jsola.contract.vo.ContractVO;
import org.jsola.exception.ParamException;
import org.jsola.hr.common.CacheKeyKit;
import org.jsola.hr.dto.SiteContractQuotaUpdateDTO;
import org.jsola.hr.entity.*;
import org.jsola.hr.exception.HrException;
import org.jsola.hr.provider.IApproProviderService;
import org.jsola.hr.provider.IContractProviderService;
import org.jsola.hr.provider.ISalaryTemplateProviderService;
import org.jsola.hr.service.*;
import org.jsola.hr.service.notice.ISendNoticeService;
import org.jsola.hr.vo.SiteContractQuotaVO;
import org.jsola.salary.vo.SalaryTemplateVO;
import org.jsola.user.core.TokenUser;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Component
@Slf4j
public class AsyncCompanyFature {

    @Autowired
    private ISocProgService socProgService;

    @Autowired
    private IContractProviderService contractProviderService;

    @Autowired
    private ISalaryTemplateProviderService salaryTemplateProviderService;

    @Autowired
    private ISalaryCalculateRuleGroupService salaryCalculateRuleGroupService;

    @Autowired
    private IApproProviderService approProviderService;

    @Autowired
    @Lazy
    private ICustomTemplateService customTemplateService;

    @Autowired
    private IAsyncTaskDetailService asyncTaskDetailService;

    @Autowired
    private IAsyncTaskService asyncTaskService;

    @Autowired
    private ISignService signService;

    @Autowired
    private ICompanyService companyService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ContractAsync contractAsync;

    @Autowired
    private ISendNoticeService sendNoticeService;

    @Autowired
    private ISaleRebatesRulesService saleRebatesRulesService;

    @Autowired
    private ICompanySaleRebatesRulesService companySaleRebatesRulesService;

    @Autowired
    private ICompanySaleUserService companySaleUserService;

    @Autowired
    private ICompanySaleServerService companySaleServerService;

    @Autowired
    private ISiteContractQuotaService siteContractQuotaService;

    /**
     * 查询参保方案
     *
     * @param companyId
     * @param tokenUser
     * @return
     * @throws InterruptedException
     */
    @Async
    public Future<List<SocProgDO>> taskSocProg(String companyId, TokenUser tokenUser) throws InterruptedException {
        List<SocProgDO> socProgList = new ArrayList<>();
        List<SocProgDO> socProgDOList = socProgService.selectSocProgByCompanyId(companyId, tokenUser);
        if (!CollectionUtils.isEmpty(socProgDOList)) {
            socProgList = socProgDOList.stream().filter(s -> Boolean.TRUE.equals(s.getIssue()))
                    .collect(Collectors.toList());
        }
        return new AsyncResult<List<SocProgDO>>(socProgList);
    }

    /**
     * 查询合同模板
     *
     * @param companyId
     * @param tokenUser
     * @return
     * @throws InterruptedException
     */
    @Async
    public Future<List<CategoryDO>> taskCategory(String companyId, TokenUser tokenUser) throws InterruptedException {
        List<CategoryDO> categoryList = new ArrayList<>();
        List<CategoryDO> categoryDOList = contractProviderService.listBySubjectId(companyId, tokenUser.getSiteId());
        if (!CollectionUtils.isEmpty(categoryDOList)) {
            categoryList = categoryDOList.stream().filter(s -> Boolean.TRUE.equals(s.getIssue()))
                    .collect(Collectors.toList());
        }
        return new AsyncResult<List<CategoryDO>>(categoryList);
    }

    /**
     * 工资表模板
     *
     * @param companyId
     * @param tokenUser
     * @return
     * @throws InterruptedException
     */
    @Async
    public Future<List<SalaryTemplateVO>> taskSalaryTemplate(String companyId, TokenUser tokenUser) throws InterruptedException {
        List<SalaryTemplateVO> salaryTemplateList = new ArrayList<>();
        List<SalaryTemplateVO> salaryTemplateVOList = salaryTemplateProviderService.selectByCompanyId(companyId, tokenUser);
        if (!CollectionUtils.isEmpty(salaryTemplateVOList)) {
            salaryTemplateList = salaryTemplateVOList.stream().filter(s -> Boolean.TRUE.equals(s.getIssue()))
                    .collect(Collectors.toList());
        }
        return new AsyncResult<List<SalaryTemplateVO>>(salaryTemplateList);
    }

    /**
     * 查询合同印章
     *
     * @param companyId
     * @param tokenUser
     * @return
     * @throws InterruptedException
     */
    @Async
    public Future<List<SealDO>> taskSeal(String companyId, TokenUser tokenUser) throws InterruptedException {
        List<SealDO> sealList = new ArrayList<>();
        List<SealDO> sealDOList = contractProviderService.selectBySubjectId(companyId, tokenUser.getSiteId());
        if (!CollectionUtils.isEmpty(sealDOList)) {
            sealList = sealDOList.stream().filter(s -> Boolean.TRUE.equals(s.getIssue()))
                    .collect(Collectors.toList());
        }
        return new AsyncResult<List<SealDO>>(sealList);
    }

    /**
     * 工价/劳务费规则
     *
     * @param companyId
     * @param tokenUser
     * @return
     * @throws InterruptedException
     */
    @Async
    public Future<List<SalaryCalculateRuleGroupDO>> taskSalaryCalculateRuleGroup(String companyId, TokenUser tokenUser) throws InterruptedException {
        List<SalaryCalculateRuleGroupDO> salaryCalculateRuleGroupDOList = salaryCalculateRuleGroupService
                .selectHadSourceByCompanyId(companyId, tokenUser);
        return new AsyncResult<List<SalaryCalculateRuleGroupDO>>(salaryCalculateRuleGroupDOList);
    }

    /**
     * 审批
     *
     * @param companyId
     * @param tokenUser
     * @return
     * @throws InterruptedException
     */
    @Async
    public Future<List<ApproFlowListVO>> taskApproFlowList(String companyId, TokenUser tokenUser) throws InterruptedException {
        List<ApproFlowListVO> approFlowListVOList = approProviderService
                .selectAllFlowsByCompany(companyId, tokenUser.getSiteId());
        if (!CollectionUtils.isEmpty(approFlowListVOList)) {
            approFlowListVOList = approFlowListVOList.stream().filter(s -> StringUtils.hasLength(s.getSourceId()))
                    .collect(Collectors.toList());
        }
        return new AsyncResult<List<ApproFlowListVO>>(approFlowListVOList);
    }

    /**
     * 入职模板
     *
     * @param companyId
     * @param tokenUser
     * @return
     * @throws InterruptedException
     */
    @Async
    public Future<List<CustomTemplateDO>> taskcustomTemplate(String companyId, TokenUser tokenUser) throws InterruptedException {
        List<CustomTemplateDO> customTemplateDOList = customTemplateService.selectByCompanyId(companyId, null, null, tokenUser);
        if (!CollectionUtils.isEmpty(customTemplateDOList)) {
            customTemplateDOList = customTemplateDOList.stream().filter(s -> StringUtils.hasLength(s.getSiteId()))
                    .collect(Collectors.toList());
        }
        return new AsyncResult<List<CustomTemplateDO>>(customTemplateDOList);
    }


    public void privatePublishTemplateSignAsync(PublishSignDTO publishSignDTO, TokenUser tokenUser) {
        String key = CacheKeyKit.getPublishTemplateSignKey(publishSignDTO.getSubjectId());
        RLock lock = redissonClient.getLock(key);
        boolean hasLock;
        try {
            // 加锁，等待0秒，上锁以后1小时自动解锁
            hasLock = lock.tryLock(0, 1, TimeUnit.HOURS);
            if (!hasLock) {
                throw new HrException("正在发起合同，请稍后操作");
            }
            List<MemberVariableDTO> listList = publishSignDTO.getMemberVariableList().parallelStream()
                    .filter(distinctByKey(MemberVariableDTO::getMemberId)).collect(Collectors.toList());
            if (listList.size() != publishSignDTO.getMemberVariableList().size()) {
                String collect = publishSignDTO.getMemberVariableList().stream()
                        .map(MemberVariableDTO::getMemberId).collect(Collectors.joining(","));
                log.info("发起合同的人员userid:{}", collect);
            }
            //记录任务
            AsyncTaskDO asyncTaskDO = new AsyncTaskDO();
            asyncTaskDO.setTaskName("用工单位：【" + publishSignDTO.getSubjectName() + "】-批量发起合同签署-" + DateKit8.format(new Date(), "yyyy-MM-dd HH:mm"));
            asyncTaskDO.setTaskStart(new Date());
            asyncTaskDO.setTotalTask(listList.size());
            asyncTaskDO.setTaskStatus(TaskStatusEnum.JIN_XING_ZHONG.getValue());
            asyncTaskDO.setTaskType(AsyncTaskTypeEnum.CONTRACT_PUBLISH.getValue());
            asyncTaskDO.setTaskMethod("privatePublishTemplateSignAsync");
            asyncTaskService.save(asyncTaskDO, tokenUser.getSiteId(), tokenUser.getUserId());
            String taskId = asyncTaskDO.getId();
            for (MemberVariableDTO memberVariableDTOS : listList) {
                PublishSignSigngleDTO publishSignSigngleDTO = publishSignDTO.to(PublishSignSigngleDTO.class);
                publishSignSigngleDTO.setMemberVariableDTO(memberVariableDTOS);
                long time = System.currentTimeMillis();
                log.info("线程名称：{},异步发起合同开始,名称：{}", Thread.currentThread().getName(), publishSignSigngleDTO.getMemberVariableDTO().getContractName());
                AsyncTaskDetailDO asyncTaskDetailDO = new AsyncTaskDetailDO();

                try {
                    // 校验电子合同余额
                    checkContractQuota(publishSignSigngleDTO, tokenUser);
                    // TODO 实际签署接口
                    List<ContractVO> contractVOList = signService.publishTemplateSignSingle(publishSignSigngleDTO, Boolean.FALSE, tokenUser);
                    List<SignWeiXinSendDTO> sendList = new ArrayList<>();
                    for (ContractVO contractVO : contractVOList) {
                        // 没有乙方直接跳过
                        if (contractVO.getPartyBPhone() == null) {
                            continue;
                        }
                        // 同步花名册电子合同
                        contractProviderService.saveEmpContract(contractVO, tokenUser);
                        // 如果是企业单方签署这里要要调确认签署接口,把合同签完
                        //List<PartySignAccountVO> partySignAccount = contractProviderService
                        //        .findPartySignAccount(contractVO.getId(), tokenUser.getSiteId());
                        // 找出企业方的accountId
                        //List<PartySignAccountVO> accountList = partySignAccount.stream()
                        //        .filter(account -> 2 == account.getType()).collect(Collectors.toList());
                        // TODO 原先的员工单方签署逻辑
                        //if (SignMethod.ENTERPRISE_ONLY.getValue().equals(category.getSignType())) {
                        //    contractProviderService.enterpriseSignAgain(contractVO.getId(),
                        //            accountList.get(0).getMemberId(), tokenUser);
                        //    contractProviderService.setJpgToAppendix(contractVO.getId(), tokenUser);
                        //    // 签署完成需要发送微信消息通知
                        //    SignWeiXinSendDTO signWeiXinSend = new SignWeiXinSendDTO();
                        //    signWeiXinSend.setContractId(contractVO.getId());
                        //    signWeiXinSend.setContractName(memberVariableDTOS.getContractName());
                        //    // 甲乙方名称
                        //    signWeiXinSend.setAName(memberVariableDTOS.getPartyAName());
                        //    signWeiXinSend.setAUserId(memberVariableDTOS.getMemberId());
                        //    signWeiXinSend.setBName(memberVariableDTOS.getPartyBName());
                        //    sendList.add(signWeiXinSend);
                        //}
                    }
                    try {
                        //sendNoticeService.sendWaitViewWeiXinTemp(sendList, tokenUser);
                    } catch (Exception e) {
                        log.error("发起合同,企业单方签署,发送短信失败", e);
                    }

                    // 完成一个子任务，记录一下
                    asyncTaskDetailDO.setTaskId(taskId);
                    asyncTaskDetailDO.setSubjectId(publishSignSigngleDTO.getMemberVariableDTO().getMemberId());
                    asyncTaskDetailDO.setSubjectResult(TaskSubjectResultEnum.SUCCESS.getValue());

                } catch (Exception e) {
                    log.error("线程名称：{},异步发起合同异常,名称{}，用时：{}, 异常", Thread.currentThread().getName(),
                            publishSignSigngleDTO.getMemberVariableDTO().getContractName(), System.currentTimeMillis() - time,
                            e);
                    // 完成一个子任务，记录一下
                    asyncTaskDetailDO.setTaskId(taskId);
                    asyncTaskDetailDO.setSubjectId(publishSignSigngleDTO.getMemberVariableDTO().getMemberId());
                    asyncTaskDetailDO.setSubjectResult(TaskSubjectResultEnum.FAIL.getValue());
                    asyncTaskDetailDO.setErrorMessage(e.getMessage());
                    if (e instanceof NullPointerException) {
                        asyncTaskDetailDO.setErrorMessage("NullPointerException");
                    }
                    asyncTaskService.taskFail(taskId);
                } finally {
                    asyncTaskDetailService.save(asyncTaskDetailDO, tokenUser.getSiteId(), tokenUser.getUserId());
                    asyncTaskService.finishSubTask(taskId);
                    log.info("线程名称：{},异步发起合同结束,名称{}，用时：{}", Thread.currentThread().getName(), publishSignSigngleDTO.getMemberVariableDTO().getContractName(), System.currentTimeMillis() - time);
                }
            }
        } catch (InterruptedException e) {
            log.error("发起合同加锁异常：", e);
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 校验电子合同余额
     */
    private void checkContractQuota(PublishSignSigngleDTO publishSignSigngleDTO, TokenUser tokenUser) {
        log.info("privatePublishTemplateSignAsync 开始发起合同，校验合同余额");
        SiteContractQuotaVO quotaVO = siteContractQuotaService.selectBySite(tokenUser.getSiteId());
        // 未查到合同余量 直接通过
        ParamException.notNull(quotaVO, "未配置合同份额，请联系管理员处理");
        // 本次签署量
        int size = 1;
        // 人脸签署量
        int faceSize = 0;
        // 合同余额
        Integer contractCount = quotaVO.getContractCount();
        ParamException.isTrue(contractCount >= size, "电子合同余额不足，请联系管理员进行充值");
        if (Boolean.TRUE.equals(publishSignSigngleDTO.getIsCheckFace())) {
            faceSize = 1;
            // 人脸余额
            Integer faceCount = quotaVO.getFaceCount();
            ParamException.isTrue(faceCount >= faceSize, "人脸验证余额不足，请联系管理员进行充值");
        }
        // 扣减份额
        SiteContractQuotaUpdateDTO updateDTO = new SiteContractQuotaUpdateDTO();
        updateDTO.setId(quotaVO.getId());
        updateDTO.setCurrentContractCount(-size);
        updateDTO.setCurrentFaceCount(-faceSize);
        siteContractQuotaService.reduce(updateDTO, tokenUser);
        log.info("privatePublishTemplateSignAsync 合同余额校验完成");
    }

    static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    //companyId 只指用工单位id, accountId 是合同模板印章中认证的公司信息
    @Async
    public void enterpriseSignAgainBatch(List<Long> contractIds, String companyId, String accountId, TokenUser tokenUser) {
        CompanyDO companyDO = companyService.selectDOById(companyId, tokenUser.getSiteId());
        log.info("线程名称：{},异步公司签署合同开始,公司名称：{}", Thread.currentThread().getName(), companyDO.getName());

        String key = CacheKeyKit.getEnterpriseSignKey(companyId);
        RLock lock = redissonClient.getLock(key);
        boolean hasLock;
        try {
            // 加锁，等待0秒，上锁以后1小时自动解锁
            hasLock = lock.tryLock(0, 1, TimeUnit.HOURS);
            if (!hasLock) {
                throw new HrException("正在确认签署合同，请稍后操作");
            }
        } catch (InterruptedException e) {
            log.error("公司签署合同加锁异常：", e);
            throw new RuntimeException(e);
        }

        //记录任务
        AsyncTaskDO asyncTaskDO = new AsyncTaskDO();
        asyncTaskDO.setTaskName("用工单位：【" + companyDO.getName() + "】-批量确认签署合同-" + DateKit8.format(new Date(), "yyyy-MM-dd HH:mm"));
        asyncTaskDO.setTaskStart(new Date());
        asyncTaskDO.setTotalTask(contractIds.size());
        asyncTaskDO.setTaskStatus(TaskStatusEnum.JIN_XING_ZHONG.getValue());
        asyncTaskDO.setTaskType(AsyncTaskTypeEnum.GONG_SI_QIAN_SHU.getValue());
        asyncTaskDO.setTaskMethod("enterpriseSignAgainBatch");
        asyncTaskService.save(asyncTaskDO, tokenUser.getSiteId(), tokenUser.getUserId());

        CountDownLatch countDownLatch = new CountDownLatch(contractIds.size());
        for (Long contractId : contractIds) {
            contractAsync.contractSignAgainAsync(contractId, accountId, tokenUser,
                    asyncTaskDO.getId(), countDownLatch);
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.unlock();
        log.info("线程名称：{},异步公司签署合同结束,公司名称：{}", Thread.currentThread().getName(), companyDO.getName());
    }

    /**
     * 返费规则
     */
    @Async
    public Future<SaleRebatesRulesDO> selectCompanyRules(String companyId, TokenUser tokenUser) {
        SaleRebatesRulesDO saleRebatesRules = null;
        CompanySaleRebatesRulesDO companySaleRebatesRules = companySaleRebatesRulesService
                .selectRulesByCompanyId(companyId, tokenUser.getSiteId());
        if (!ObjectUtils.isEmpty(companySaleRebatesRules)) {
            saleRebatesRules = saleRebatesRulesService
                    .selectDOById(companySaleRebatesRules.getRebatesRulesId(), tokenUser.getSiteId());
        }
        return new AsyncResult<>(saleRebatesRules);
    }

    /**
     * 所属销售
     */
    @Async
    public Future<List<CompanySaleUserDO>> selectCompanySaleUser(String companyId, TokenUser tokenUser) {
        List<CompanySaleUserDO> companySaleUserList = companySaleUserService
                .selectSaleUserByCompanyId(companyId, tokenUser.getSiteId());
        return new AsyncResult<>(companySaleUserList);
    }

    /**
     * 合同信息
     */
    @Async
    public Future<List<CompanySaleServerDO>> selectCompanySaleServer(String companyId, TokenUser tokenUser) {
        List<CompanySaleServerDO> companySaleServerList = companySaleServerService
                .selectCompanyServerList(companyId, tokenUser.getSiteId());
        return new AsyncResult<>(companySaleServerList);
    }

}
